package LexicalAnalyzer;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Lexer class; this program outputs a stream of tokens based on a given input stream from either stdin or a file specified in the command line.  
 * @author akanter, vcolegro
 * We affirm we have adhered to the Honor Code in this assignment. 
 */
public class Lexer implements Iterator<Token>{

	/**
	 * @param args[0], a file from which to process input. 
	 */
	FileReader fileReader;
	LinkedList<Token> tokenList = new LinkedList<Token>();
	Iterator<Token> tokenIterator = null;
	Token peekToken;
	static boolean prevTokenNegative = false;
	static long LINENUMBER = 1;	
	static final String TOKENS[][] = {
		{"id",												"[a-zA-Z_]([a-zA-Z_]|[0-9])*"},
		{"keyword", 										"int|float|void|if|else|while|for|return"},
		{"comment",											"/\\*(.|\\s)*?\\*/"},
		{"punctuation",										"\\{|\\}|\\(|\\)|\\[|]|;|,"},
		{"boolop",											"&&|\\|\\||>=|<=|==|!=|>|<|!"},
		{"op",												"\\+\\+|--|\\+=|-=|%|\\+|-|\\*|/|=|&"},

		{"real",											"([0-9]+\\.[0-9]*(e|E)(\\+|-)?[0-9]+)|" + //Optional fraction, with exp
															"([0-9]*\\.[0-9]\\+(e|E)(\\+|-)?[0-9]+)|" + //Optional int, with exp
															"([0-9]+(e|E)(\\+|-)?[0-9]+)|" + //No decimal point, fraction/int are merged
															"([0-9]+\\.[0-9]*)|" + //Optional fraction, no exp
															"([0-9]*\\.[0-9]+)"}, //Optional int, no exp
		{"hex",												"0x[0-9a-fA-F]+"},
		{"digit",											"[0-9]+"},
		{"invalid",											"[^-a-zA-Z_0-9+\\\\><(){}\n\t\r=&*/%!,;\\[\\] ]"} //invalid characters
	};

	/**
	 * Constructor 1; calls Constructor 2, passing specified file name as a file descriptor. 
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public Lexer(String filename) throws FileNotFoundException, IOException{
		this(new FileInputStream(new File(filename)).getFD());
	}

	/**
	 * Constructor 2
	 * @param fd; file descriptor for input source. 
	 */
	public Lexer(FileDescriptor fd){
		try{
			this.fileReader = new FileReader(fd);
			StringBuilder sb = new StringBuilder();
			int read;
			while (fd.valid() && (read = fileReader.read()) > -1){
				sb.append((char) read);
			}
			
			//PATTERNS
			Pattern[] patternArray = {
					Pattern.compile(TOKENS[0][1]),
					Pattern.compile(TOKENS[1][1]),
					Pattern.compile(TOKENS[2][1]),
					Pattern.compile(TOKENS[3][1]),
					Pattern.compile(TOKENS[4][1]),
					Pattern.compile(TOKENS[5][1]),
					Pattern.compile(TOKENS[6][1]),
					Pattern.compile(TOKENS[7][1]),
					Pattern.compile(TOKENS[8][1]),
					Pattern.compile(TOKENS[9][1])
					};
			
			Matcher matcher;
			
			//loop to divide stringbuilder into tokens
			while (sb.length()>0){
				
				if (sb.charAt(0) == '\n'|| sb.charAt(0) == '\r') LINENUMBER++;
				if (Character.isWhitespace(sb.charAt(0))){
					sb.deleteCharAt(0);
					prevTokenNegative = false;
					continue;
				}
				
				
				for (int i=0;i<patternArray.length;i++){
					matcher = patternArray[i].matcher(sb);
					if (matcher.lookingAt()){ //if we've found a substring that matches a pattern (i.e. found a token)
						String token = (String) sb.subSequence(matcher.start(), matcher.end());
						sb.delete(matcher.start(), matcher.end());
						
						//if we are matching an id, check that it's not a keyword
						if (i==0 && patternArray[1].matcher(token).matches()){
							addToken(token, 1);
						}
						else addToken(token, i);
					}
					
					
				}
				
			}
			tokenIterator = tokenList.iterator();
		}
		catch (FileNotFoundException e){
			e.printStackTrace();
		}
		catch (IOException e){
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	
	/**
	 * @param input, String version of token
	 * @param type, specifies which pattern the Token matched, delineates Token type. 
	 * @throws Exception If invalid characters are used 
	 */
	private void addToken(String input, int type) throws Exception{
		Token t = null;
		switch (type){
		case 0: t = new Identifier(input); 
			break;
		case 1: t = new Keyword(input);
			break;
		case 2: t = new Comment(input);
			break;
		case 3: t = new Punctuation(input); 
			break;
		case 4: t = new BoolOp(input);
			break;
		case 5:	t = new Op(input);
			if (input.charAt(0)=='-') prevTokenNegative=true;  //for use in making negative ints
			break;
		case 6: t = new Constant(input, 6);
			break;
		case 7: t = new Constant(input, 7);
			break;
		case 8: 	
			try{
				if ( (Lexer.prevTokenNegative && (Long.parseLong(input) > 2147483648L)) || (!Lexer.prevTokenNegative && (Long.parseLong(input) >= 2147483648L) )){
					System.err.println(LINENUMBER + ": Value of \'" + input + "\' out of range for type Int");
					t = new Error("Value of \'" + input + "\' out of range for type Int");
				}
				else t = new Constant(input, 8);
			}
			catch (NumberFormatException e){
				System.err.println(LINENUMBER + ": Value of \'" + input + "\' out of range for type Int");
				t = new Error("Value of \'" + input + "\' out of range for type Int");
			}
			break;
		case 9: t = new Error(input);
			System.err.println(LINENUMBER + ": Invalid character \'" + input + '\'');
			break;
		}
		if (t!=null) tokenList.add(t);
		t=null;
		if (type!=5 || input.charAt(0)!='-') prevTokenNegative=false;
	}

	/**
	 * Main method; testing purposes only. 
	 */
	public static void main(String[] args) {

		Lexer lex = null;
		
		if (args.length>0){ //input source is file.
			try {
				lex = new Lexer(args[0]);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

		} else{
			System.out.println("Enter text to lex, then ctrl+d (twice if not after a new line):\n");
			lex = new Lexer(FileDescriptor.in);
		}

		//print results
		System.out.println("\nLex results:\n");
		while (lex.hasNextToken()){  
			System.out.print(lex.getNextToken() + " ");
		}
		System.out.println();

	}
	
	/**
	 * @return boolean whether there is a next token
	 */
	public boolean hasNextToken(){
		if (peekToken!=null) return true;
		return this.tokenIterator.hasNext();
	}
	
	/**
	 * @return The next token, but without incrementing the Token list
	 */
	public Token peekNextToken(){
		if (peekToken!=null) return peekToken;
		if (hasNextToken()){
			peekToken = getNextToken();
			return peekToken;
		}
		return null;
	}

	/**
	 *@return the next token in the list
	 */
	public Token getNextToken(){
		if (peekToken!=null){
			Token temp = peekToken;
			peekToken = null;
			return temp;
		}
		return this.tokenIterator.next();

	}

	/**
	 * @return boolean whether there is a next token
	 */
	@Override
	public boolean hasNext() {
		return hasNextToken();
	}


	/**
	 * @return the next token in the list
	 */
	@Override
	public Token next() {
		return getNextToken();
	}


	/**
	 * @return
	 */
	@Override
	public void remove() {
		// DOES NOTHING. MUAH HA HA HA HA HA!
		
	}

}
	

